package com.geega.geetest.geetest;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

import static com.alibaba.fastjson.JSON.parseObject;

/**
 * Java SDK
 */
public class GeetestLib {

    protected final String verName = "4.0";
    protected final String sdkLang = "java";

    private static String apiUrl = "";

    protected final String registerUrl = "/register.php";
    protected final String validateUrl = "/validate.php";

    /**
     * 极验验证二次验证表单数据 challenge
     */
    public static final String fn_geetest_challenge = "challenge";
    private final Logger log = LoggerFactory.getLogger(GeetestLib.class);

    /**
     * 公钥
     */
    private String cid = "";

    /**
     * 私钥
     */
    private String cKey = "";

    /**
     * token私钥
     */
    private String tokenKey = "Geetest";

    /**
     * 返回字符串
     */
    private String responseStr = "";

    /**
     * 调试开关，是否输出调试日志
     */
    private boolean debugCode = true;

    /**
     * 极验验证API服务状态Session Key
     */
    public String gtServerStatusSessionKey = "gt_server_status";


    private static String staticServerstr = "";

    private static Object apiServer = "";

    // 宕机时加载该js资源，需要保证能通过静态资源服务器正常访问
    private static String fullpage = "www/js/fullpage.0.0.0.js";
    // 宕机时加载该css资源，需要保证能通过静态资源服务器正常访问
    private static String css = "www/css/wind/style.0.0.0.css";

    /**
     * 带参数构造函数
     *
     * @param cid:  验证码CID
     * @param cKey: 验证码CKEY
     */
    public GeetestLib(String cid, String cKey) {
        this.cid = cid;
        this.cKey = cKey;
    }


    /**
     * 获取api的Url
     *
     * @param configJson: 配置文件路径
     */
    private static void getApiUrl(JSONObject configJson) {
//        Properties properties = new Properties();
//        try {
//            FileInputStream in = new FileInputStream(configpath);
//            properties.load(in);
//            in.close();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//        apiUrl = properties.getProperty("apiUrl");
        apiUrl = configJson.getString("apiUrl");
    }

    /**
     * 获取本次验证初始化返回字符串
     *
     * @return 初始化结果
     */
    public String getResponseStr() {
        return responseStr;
    }

    public String getVersionInfo() {
        return verName;
    }

    private static ArrayList<Object> strToList(String serverStr) {
        String[] servers = serverStr.split(";");
        ArrayList<Object> serverList = new ArrayList<>();
        Collections.addAll(serverList, servers);
        return serverList;
    }

    /**
     * 获取配置参数
     *
     * @param configJson: 配置文件路径
     */
    private static void getProperties(JSONObject configJson) {
//        Properties properties = new Properties();
//        try {
//            FileInputStream in = new FileInputStream(configpath);
//            properties.load(in);
//            in.close();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//        static_serverstr = properties.getProperty("static_servers");
//        api_server = properties.getProperty("api_server");

        staticServerstr = configJson.getString("static_servers");
        apiServer = configJson.getString("api_server");
    }

    /**
     * 验证初始化预处理
     *
     * @param data       自定义参数
     * @param configJson 配置路径
     * @return 返回服务端的源数据，业务端通过status判断是否需要走离线模式，status为0表示注册成功，status为1表示注册失败
     */
    public String preProcess(HashMap<String, String> data, JSONObject configJson) {
        // 注册产生challenge
        getApiUrl(configJson);
        String result = registerChallenge(data);
        gtlog("result:" + result);
        JSONObject resultData;
        try {
            resultData = parseObject(result);
            int status = (int) resultData.get("status");
            // 根据响应内容中的status判断服务是否正常，status为0即表示服务正常，status为1表示服务异常由SDK构建响应内容
            if (status == 1) {
                getProperties(configJson);
                this.responseStr = getFailPreProcessRes();
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return result;

    }

    /**
     * 获得验证初始化结果
     *
     * @param response 验证响应体
     * @return 返回对应register接口的数据
     */
    private String registerResult(String response) {
        try {
            JSONObject return_map = parseObject(response);
            int status = return_map.getInteger("status");
            if (status == 0) {
                // 返回data的json字符串
                this.responseStr = response;
                gtlog("responseStr: " + this.responseStr);
                return response;
            } else {
                gtlog("gtServer register challenge error");
                return response;
            }

        } catch (JSONException e) {
            gtlog("json load error");
            JSONObject result = new JSONObject();
            try {
                result.put("status", 1);
                result.put("error_code", 10035);
                result.put("error_msg", "网络不给力");
            } catch (JSONException e1) {
                e1.printStackTrace();
            }
            return result.toString();
        }
    }

    /**
     * 预处理失败后的返回格式串
     *
     * @return
     */
    private String getFailPreProcessRes() {
        long rnd = Math.round(Math.random() * 100);
        String challenge = sha256Encode(rnd + "");
        JSONObject jsonObject = new JSONObject();
        JSONObject data = new JSONObject();
        try {
            ArrayList<Object> staticServers = strToList(staticServerstr);
            data.put("cid", this.cid);
            data.put("challenge", challenge);
            data.put("type", "fullpage");
            data.put("static_servers", staticServers);
            data.put("offline", true);
            data.put("fullpage", fullpage);
            data.put("css", css);
            jsonObject.put("status", 0);
            jsonObject.put("data", data);
        } catch (JSONException e) {
            gtlog("json dumps error");
        }
        return jsonObject.toString();
    }

    /**
     * 用cid进行注册，更新challenge
     *
     * @param data 自定义参数
     * @return status为0表示注册成功，status为1表示注册失败
     */
    private String registerChallenge(HashMap<String, String> data) {

        try {
            String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
            String token = makeToken(timestamp);

            String postUrl = apiUrl + registerUrl;
            gtlog("this.cid:" + this.cid);
            data.put("cid", this.cid);
            data.put("token", token);
            data.put("timestamp", timestamp);
            JSONObject params = parseObject(JSON.toJSONString(data));
            gtlog("param:" + params.toString());
            String response = "";
            gtlog("postUrl:" + postUrl);
            response = readContentFromPost(postUrl, params.toString());
            gtlog("response: " + response);
            return registerResult(response);
        } catch (Exception e) {
            gtlog(e.toString());
            gtlog("exception:register api");
            JSONObject failReturn = new JSONObject();
            try {
                failReturn.put("status", 1);
            } catch (JSONException e1) {
                e1.printStackTrace();
            }
            return failReturn.toString();
        }
    }


    /**
     * 判断一个表单对象值是否为空
     *
     * @param gtObj
     * @return
     */
    private boolean objIsEmpty(Object gtObj) {

        if (gtObj == null) {
            return true;
        }

        return gtObj.toString().trim().length() == 0;
    }

    /**
     * 检查客户端的请求是否合法
     *
     * @param
     * @return
     */
    private boolean requestIsLegal(String challenge) {

        return objIsEmpty(challenge);
    }


    /**
     * 服务正常的情况下使用的验证方式,向gt-server进行二次验证,获取验证结果
     *
     * @param challenge
     * @return 验证结果, 根据status判断，0表示验证成功1表示验证失败
     */
    public String enhencedValidateRequest(String challenge, HashMap<String, String> data, JSONObject configJson) {
        String response = "";

        try {
            // 判断challenge是否合法 不合法则返回1
            if (requestIsLegal(challenge)) {
                JSONObject result = new JSONObject();
                result.put("status", 1);
                result.put("data", new JSONObject().put("result", "fail"));
                return result.toString();
            }
            getApiUrl(configJson);
            gtlog("request legitimate");
            String postUrl = apiUrl + this.validateUrl;
            String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
            String token = makeToken(timestamp);
            data.put("cid", this.cid);
            data.put("token", token);
            data.put("timestamp", timestamp);
            data.put("challenge", challenge);
            JSONObject paramJson = parseObject(JSON.toJSONString(data));
            gtlog("param:" + paramJson.toString());
            response = readContentFromPost(postUrl, paramJson.toString());
            gtlog("response: " + response);
            // 根据响应中的result进行判断验证结果
            String return_result = "";
            try {
                JSONObject return_map = parseObject(response);
                String return_data = return_map.get("data").toString();
                JSONObject data_map = parseObject(return_data);
                gtlog("return_data: " + return_data);
                return_result = data_map.get("result").toString();
                gtlog("return_result: " + return_result);
                if (!return_result.equals("success")) {
                    return_map.put("status", 1);
                    response = return_map.toString();
                } else {
                    response = return_map.toString();
                }
            } catch (JSONException e) {
                gtlog("json load error");
                return response;

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return response;

    }

    /**
     * failback使用的验证方式
     *
     * @param challenge
     * @return 验证结果, 0表示验证成功1表示验证失败
     */
    public String failbackValidateRequest(String challenge) {

        gtlog("in failback validate");
        JSONObject result = new JSONObject();

        try {
            if (requestIsLegal(challenge)) {
                result.put("status", 1);
                result.put("data", new JSONObject().put("result", "fail"));
                return result.toString();
            }
            gtlog("request legitimate");
            result.put("status", 0);
            result.put("data", new JSONObject().put("result", "success"));
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return result.toString();
    }

    /**
     * 输出debug信息，需要开启debugCode
     *
     * @param message
     */
    public void gtlog(String message) {
        if (debugCode) {
            log.debug("this.gtlog:{} " + message);
        }
    }


    /**
     * 发送POST请求，获取服务器返回结果
     *
     * @param
     * @return 服务器返回结果
     * @throws IOException
     */
    private String readContentFromPost(String URL, String data) throws IOException {

        gtlog("data:" + data);
        URL postUrl = new URL(URL);
        HttpURLConnection connection = (HttpURLConnection) postUrl
            .openConnection();

        connection.setConnectTimeout(2000);// 设置连接主机超时（单位：毫秒）
        connection.setReadTimeout(2000);// 设置从主机读取数据超时（单位：毫秒）
        connection.setRequestMethod("POST");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

        // 建立与服务器的连接，并未发送数据
        connection.connect();

        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(connection.getOutputStream(), "utf-8");
        outputStreamWriter.write(data);
        outputStreamWriter.flush();
        outputStreamWriter.close();

        if (connection.getResponseCode() == 200) {
            // 发送数据到服务器并使用Reader读取返回的数据
            StringBuffer sBuffer = new StringBuffer();

            InputStream inStream = null;
            byte[] buf = new byte[1024];
            inStream = connection.getInputStream();
            for (int n; (n = inStream.read(buf)) != -1; ) {
                sBuffer.append(new String(buf, 0, n, "UTF-8"));
            }
            inStream.close();
            connection.disconnect();// 断开连接

            return sBuffer.toString();
        } else {

            JSONObject result = new JSONObject();
            try {
                result.put("status", 1);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            return result.toString();
        }
    }

    /**
     * 产生token
     *
     * @param timeStamp
     * @return
     */
    public String makeToken(String timeStamp) {
        String token_str = String.format("%s%s%s", cKey, tokenKey, timeStamp);
        String token = sha256Encode(token_str);
        gtlog("token--->" + token);
        return token;
    }

    public static boolean isNotnull(String str) {
        if (str == null || str.isEmpty()) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * 利用java原生的摘要实现SHA256加密
     *
     * @param str 加密后的报文
     * @return
     */
    public static String sha256Encode(String str) {
        MessageDigest messageDigest;
        String encodeStr = "";
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(str.getBytes("UTF-8"));
            encodeStr = byte2Hex(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return encodeStr;
    }

    /**
     * 将byte转为16进制
     *
     * @param bytes
     * @return
     */
    private static String byte2Hex(byte[] bytes) {
        StringBuffer stringBuffer = new StringBuffer();
        String temp = null;
        for (int i = 0; i < bytes.length; i++) {
            temp = Integer.toHexString(bytes[i] & 0xFF);
            if (temp.length() == 1) {
                //1得到一位的进行补0操作
                stringBuffer.append("0");
            }
            stringBuffer.append(temp);
        }
        String result = stringBuffer.toString().substring(0, 32);
        return result;
    }

}
